﻿/**
 * Peter
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 * 
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 *
 *  This code is provided on an AS IS basis, with no WARRANTIES,
 *  CONDITIONS or GUARANTEES of any kind.
 *
 **/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using Irony.Parsing;
using Peter.Common;
using Peter.Common.Code;
using Peter.Common.Interfaces;
using Peter.Common.MainMenu;
using Peter.Common.Services;
using Peter.Data;

namespace Peter.Logic
{
   /// <summary>
   /// Model for the code explorer window.
   /// </summary>
   public sealed class ModelCodeExplorer : EditablePeterToolWindowWithMenu
   {
      private string m_ParseError;
      private IPeterTextDocument m_CurrentDocument;
      private BaseCodeItem m_SelectedItem;
      private readonly ITimerService m_Timer;
      private readonly ObservableCollection<BaseCodeItem> m_Structure;

      /// <summary>
      /// Initializes a new code explorer model.
      /// </summary>
      public ModelCodeExplorer ()
      {
         this.Title = "Code Explorer";
         this.Icon = MainMenuIcon.Braces;
         this.ContentId = Guids.CODE_EXPLORER;
         this.ToolTip = "Views the structure of the current document";
         this.m_Structure = new ObservableCollection<BaseCodeItem> ();

         this.m_Timer = ServiceManager.Get<ITimerService> ();
         this.m_Timer.Interval = 500;
         this.m_Timer.OnTimerTick += this.OnUpdateTimerTick;
      }

      /// <summary>
      /// Gets the list of available grammars.
      /// </summary>
      [ImportMany (typeof (Grammar))]
      public IEnumerable<Lazy<Grammar, IGrammarMetadata>> Grammars { get; set; }

      /// <summary>
      /// Gets the list of available grammar tree parsers.
      /// </summary>
      [ImportMany (typeof (IGrammarTreeParser))]
      public IEnumerable<Lazy<IGrammarTreeParser, IGrammarMetadata>> GrammarParsers { get; set; }

      /// <summary>
      /// Gets or Sets the selected item in the tree.
      /// </summary>
      public BaseCodeItem SelectedItem
      {
         get { return m_SelectedItem; }
         set
         {
            this.m_SelectedItem = value;
            this.OnPropertyChanged ("SelectedItem");
            this.FindInDocument ();
         }
      }

      /// <summary>
      /// Gets or Sets the parser error.
      /// </summary>
      public string ParseError
      {
         get { return this.m_ParseError; }
         set
         {
            this.m_ParseError = value;
            this.OnPropertyChanged ("ParseError");
         }
      }

      /// <summary>
      /// Gets or Sets the code structure.
      /// </summary>
      public ObservableCollection<BaseCodeItem> CodeStructure { get { return this.m_Structure; } }

      /// <summary>
      /// Finds the selected item in the current document.
      /// </summary>
      private void FindInDocument ()
      {
         if (this.m_CurrentDocument != null && this.SelectedItem != null)
         {
            /*var csItem = this.SelectedItem as CSharpCodeItem;
            if (csItem != null)
               this.m_CurrentDocument.ScrollToLocation (csItem.Location);
            else*/
               this.m_CurrentDocument.ScrollToOffset (this.SelectedItem.Position);
         }
      }

      /// <summary>
      /// Occurs when the active content in peter changes.
      /// </summary>
      private void OnActiveContentChanged (object content, EventArgs eventArgs)
      {
         var document = Peter.ActiveContent as IPeterTextDocument;
         if (document != null && this.m_CurrentDocument != document)
            AttachToDocument (document);
      }

      /// <summary>
      /// Attaches the code explorer to the given document.
      /// </summary>
      /// <param name="document"></param>
      private void AttachToDocument (IPeterTextDocument document)
      {
         if (this.m_CurrentDocument != null)
            this.m_CurrentDocument.TextChanged -= this.DocumentTextChanged;

         this.CodeStructure.Clear ();
         if (document.FileInfo != null)
         {
            this.m_CurrentDocument = document;
            this.m_CurrentDocument.TextChanged += this.DocumentTextChanged;
            if (!string.IsNullOrEmpty (document.FileInfo.Extension))
            {
               Action <string, string, string> parse = this.Parse;
               parse.BeginInvoke (document.FileInfo.FullName, document.FileInfo.Extension, document.Text, null, null);
            }
         }
      }

      /// <summary>
      /// Occurs when the text of the current document changes.
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="args"></param>
      private void DocumentTextChanged (object sender, TextEventArgs args)
      {
         this.m_Timer.StopTimer ();
         this.m_Timer.Start ();
      }

      /// <summary>
      /// Occurs when the update timer ticks.
      /// </summary>
      /// <param name="sender">The timer.</param>
      /// <param name="e">EventArgs</param>
      private void OnUpdateTimerTick (object sender, EventArgs e)
      {
         this.m_Timer.StopTimer ();
         Action<string, string, string> parse = this.Parse;
         parse.BeginInvoke (this.m_CurrentDocument.FileInfo.FullName, this.m_CurrentDocument.FileInfo.Extension,
            this.m_CurrentDocument.Text, null, null);
      }

      private void Parse (string filePath, string fileExtension, string text)
      {
         this.GuiDispatcher.BeginInvoke (new Action (() => this.CodeStructure.Clear ()));
         if (File.Exists (filePath))
         {
            if (fileExtension.ToLower().Equals(".cs"))
            {
               //this.ParseCSharpCode (text);
            }
            else
            {
               var grammar = this.Grammars.FirstOrDefault(g => g.Metadata.Extensions.Contains(fileExtension));
               var parserTree = this.GrammarParsers.FirstOrDefault(g => g.Metadata.Extensions.Contains(fileExtension));
               if (grammar != null && parserTree != null)
               {
                  ParseTree tree = null;
                  var language = new LanguageData(grammar.Value);
                  var parser = new Parser(language);
                  try
                  {
                     tree = parser.Parse(File.ReadAllText(filePath));
                     if (tree.HasErrors())
                     {
                        Console.Error.WriteLine("Errors found while parsing code structure:");
                        foreach (var message in tree.ParserMessages)
                        {
                           Console.Error.WriteLine("(line:{1} column:{2} offset:{3}) {0}", message.Message,
                              message.Location.Line + 1, message.Location.Column + 1, message.Location.Position);
                        }
                     }
                  }
                  catch (Exception ex)
                  {
                     Console.Error.WriteLine(ex.Message);
                  }

                  if (tree != null)
                  {
                     var items = parserTree.Value.ParseTree(tree, this.GuiDispatcher);
                     foreach (var item in items)
                     {
                        this.GuiDispatcher.BeginInvoke(new Action<BaseCodeItem>(i => this.CodeStructure.Add(i)), item);
                     }
                  }
               }
            }
         }
      }

      /// <summary>
      /// Occurs when the instance of peter is resolved.
      /// </summary>
      protected override void PeterInstanceResolved ()
      {
         this.Peter.ActiveContentChanged += this.OnActiveContentChanged;
         var document = this.Peter.ActiveContent as IPeterTextDocument;
         if (document != null)
            this.AttachToDocument (document);
      }

      /// <summary>
      /// Checks whether an edit actions can be performed or not.
      /// </summary>
      /// <param name="obj">Edit action</param>
      /// <returns>True if action can be performed, otherwise false.</returns>
      protected override bool CanEdit (object obj)
      {
         return false;
      }

      /// <summary>
      /// Occurs when a tree item is being executed.
      /// </summary>
      /// <param name="obj">Command parameter.</param>
      protected override void OnExecute (object obj)
      {
         
      }
   }
}
